[Amazon SageMaker] イメージ分類(Image Classification)における対象物の写り具合による検出状況について
1 はじめに
CX事業本部の平内(SIN)です。
Amazon SageMakerのビルトインアルゴリズムのイメージ分類は、画像を分類するものですが、シングルラベルで作成されたモデルで、ターゲットする対象物が複数写っていたり、一部だけ写っていたりする場合の検出具合はどうなんだろう?という事で、色々試してみました。
使用したモデルは、下記のように回転台に載せて正面から撮ったデータセットから作成されたものです。
試してみた対象物(商品)は、OREOとCHEDDAR_CHEESEです。この2つは、今回作成したモデルの中でも、非常に制度が高かったものです。今回の確認結果は、精度の低いモデルでは、もっと違った結果となる可能性があることを、予めご了承下さい。
なお、画像はWebカメラで撮影し、表示されている水色の枠内だけを、推論にかけ、結果(上位3つ)を文字列で表示しています。
2 各種パターンでの検出状況
(1) 1つが正面の場合
正面から1つの商品だけ写っている場合は、99%の確率で検出できています。
(2) 2つが半々に映る場合
2つの商品が半分半分に写っている場合は、概ね検出上位の2つが、当該商品になっていますが、その割合は、映り込み具合(割合)とは、同じとは限らないようです。
(3) 2つが映る場合
2つの商品の全体が写っている場合も、概ね検出上位の2つが、当該商品になっています。こちらも、その割合は、映り込み具合(割合)に比例するわけでは無いようです。
(4) 一部が映る場合
商品の一部が映り込む場合、当該商品の特徴ある部分であれば、検出できそうです。
(5) 上の方から角度をつけて映り込む場合
少々角度がついた場合も、当該商品の特徴が見られれば、検出できるようです。
(6) 検出に失敗している例
一方、検出に失敗している例です。商品の一部しか写っていない場合や、特徴がうまく出ない角度(角度があり過ぎの場合など)では、失敗しているようです。
3 コード
確認のために使用したコードを参考に紹介させて下さい。マウスのクリックでスナップショットを取れるようにしています。
""" [イメージ分類] 対象物が写っている状態によって、どのような結果が出るかを確認してみる """ import json import datetime import cv2 from boto3.session import Session PROFILE = 'developer' END_POINT = 'sampleEndPoint' CLASSES = ['PORIPPY(GREEN)', 'OREO', 'CUNTRY_MAM', 'PORIPPY(RED)', 'BANANA' , 'CHEDDER_CHEESE', 'PRETZEL(YELLOW)', 'FURUGURA(BROWN)', 'NOIR' , 'PRIME', 'CRATZ(RED)', 'CRATZ(GREEN)', 'PRETZEL(BLACK)', 'CRATZ(ORANGE)' , 'ASPARA', 'FURUGURA(RED)', 'PRETZEL(GREEN)'] DEVICE_ID = 1 # Webカメラ HEIGHT = 600 WIDTH = 800 class SageMaker(): def __init__(self, profile, endPoint): self.__end_point = endPoint self.__client = Session(profile_name=profile).client('sagemaker-runtime') def invoke(self, image): data = self.__client.invoke_endpoint( EndpointName=self.__end_point, Body=image, ContentType='image/jpeg' ) return json.loads(data['Body'].read()) def putText(frame, x, y, text): font_size = 1.2 font_color = (255, 255, 255) cv2.putText(frame, text, (x, y), cv2.FONT_HERSHEY_SIMPLEX, font_size, font_color, 2, cv2.LINE_AA) def onClick(event, x, y, flags, frame): if event == cv2.EVENT_LBUTTONUP: now = datetime.datetime.now() cv2.imwrite(str(now) + '.jpg', frame) print("Saved.") def createArea(width, height, w, h, bias): x_1 = int(width/2-w/2) x_2 = int(width/2+w/2) y_1 = int(height/2-h/2) - bias y_2 = int(height/2+h/2) - bias return [x_1, y_1, x_2, y_2] def main(): cap = cv2.VideoCapture(DEVICE_ID) cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT) fps = cap.get(cv2.CAP_PROP_FPS) width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) print("FPS:{} WIDTH:{} HEIGHT:{}".format(fps, width, height)) # 推論の対象となるエリア area = createArea(width, height, 220, 300, 100) sageMake = SageMaker(PROFILE, END_POINT) while True: # カメラ画像取得 _, frame = cap.read() if(frame is None): continue img = frame[area[1]: area[3], area[0]: area[2]] # 推論 _, jpg = cv2.imencode('.jpg', img) results = sageMake.invoke(jpg.tostring()) # 結果の整形 probabilitys = {} for i, result in enumerate(results): probabilitys[CLASSES[i]] = result probabilitys = sorted(probabilitys.items(), key = lambda x:x[1], reverse=True) # 結果の表示 for i in range(3): (name, probability) = probabilitys[i] text = "{} {}".format(name, probability) putText(frame, 20, int(height) - 120 + i*50, text) # 対象範囲の枠表示 frame = cv2.rectangle(frame, (area[0], area[1]), (area[2], area[3]), (255, 255, 0), 3) # フレーム表示 cv2.imshow('frame', frame) # マウスクリックでスナップ撮影 cv2.setMouseCallback('frame', onClick, frame) cv2.waitKey(1) cap.release() cv2.destroyAllWindows() main()
4 最後に
正面からの撮影だけで作成したデータセットですが、角度を付けた画像などでも、比較的検出できていると思いました。
元々、検出精度の高かった商品なので、全てがこのように上手くいくとは限りませんが、とりあえずは、正面撮影だけのデータセットで始めるのも、あながち間違っていないような気がしています。
現時点の肌感覚で恐縮ですが、一旦、正面撮影のデータセットを軽易に作成し、角度や条件によって検出できない商品は、弱かった角度からの特徴を追加で学習させるような手順が、効率いいのでは? と感じでいます。